前言
前陣子寫的Redux
解析感覺寫不清楚,因此重寫一篇如何實作Redux
。
這邊必須對Redux
有些基本認識,而實作的部分都先不做錯誤處理。
createStore
首先從createStore
的部分開始,在實做createStore
前先如同往常寫好一個reducer
並把他傳入createStore
中,並對他發出一個有效的action
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const reducer = ( state = 0 , action ) => { switch ( action.type ){ case 'ADD': return state + action.payload ; default : return state ; } } let store = createStore(reducer) ; console.log(store.getState()); store.dispatch({ type : 'ADD', payload : 1 }); console.log(store.getState());
|
接下來開始實做createStore
,首先先建立getState
和dispatch
函式。
1 2 3 4 5 6 7 8 9 10 11 12
| const createStore = () => { const getState = () => { } const dispatch = () => { } return { getState , dispatch } }
|
接下來紀錄傳進來的reducer
和state
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; const getState = () => { } const dispatch = () => { } return { getState , dispatch } }
|
getState
getState
只要簡單回傳nowState
即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; const getState = () => { return nowState ; } const dispatch = () => { } return { getState , dispatch } }
|
dispatch
dispatch
接受action
把它丟給reducer
處理,並將nowState
替換成運算後的state
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; } return { getState , dispatch } }
|
為了有初始狀態,先發送一個不會被處理的action
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; } dispatch({ type : 'INIT' }) ; return { getState , dispatch } }
|
如此一來簡單的createStore
就可以運行了。
subscribe
subscribe
讓你可以讓你綁定函式在dispatch
後執行,同時回傳一個unsubscribe
函式讓你可以取消綁定。
1 2 3 4 5 6 7 8 9 10
| const listener = () => { console.log('Now state: ',store.getState()) ; } let store = createStore(reducer) ; let unsubscribe = store.subscribe(listener) ; store.dispatch({ type : 'ADD' , payload : 1 }); unsubscribe(); store.dispatch({ type : 'ADD' , payload : 1 }); console.log(store.getState());
|
首先額外建立一個陣列來保存subscribe
的所有函式,並建立subscribe
函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; let nowListener = [] ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; } const subscribe = () => { } dispatch({ type : 'INIT' }) ; return { getState , dispatch , subscribe } }
|
接下來在只要註冊事件就把該函式放入nowListener
並回傳一個函式用來註銷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; let nowListeners = [] ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; nowListeners.forEach((listener) => listener()); } const subscribe = (listener) => { nowListeners.push(listener) ; return function(){ nowListeners.splice(nowListeners.indexOf(listener),1); } } dispatch({ type : 'INIT' }) ; return { getState , dispatch , subscribe } }
|
Redux
在subscribe
部分會維持兩個陣列,一個是上次dispatch
後的註冊事件,另一個則是在下次dispatch
前新註冊的所有事件,這兩個事件陣列會在dispatch
後同步,在這邊不實作這部分。
replaceReducer
將傳進來的reducer
和目前的reducer
替換
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const reducer = ( state = 0 , action ) => { switch ( action.type ){ case 'ADD': return state + action.payload ; default : return state ; } } const newReducer = ( state = 0 , action ) => { switch ( action.type ){ case 'ADD': return state + action.payload * 2 ; default : return state ; } } let store = createStore(reducer) ; store.dispatch({ type : 'ADD' , payload : 1 }); store.replaceReducer(newReducer); store.dispatch({ type : 'ADD' , payload : 1 }); console.log(store.getState());
|
只要把nowReducer
替換成傳進來的reducer
即可,替換後要發出初始action
來獲得初始狀態。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; let nowListeners = [] ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; nowListeners.forEach((listener) => listener()); } const subscribe = (listener) => { nowListeners.push(listener) ; return function(){ nowListeners.splice(nowListeners.indexOf(listener),1); } } const replaceReducer = (reducer) => { nowReducer = reducer ; dispatch({ type : 'INIT' }) ; } dispatch({ type : 'INIT' }) ; return { getState , dispatch , subscribe , replaceReducer } }
|
如此一來基本的createStore
就實作完成了,接下來實作combineReducer
的部分。
combineReducer
由於reducer
可能不只一個,因此必須將reducers
合併為一個。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const personReducer = ( state = {} , action ) => { switch ( action.type ){ case 'NAME': return {...state, name : action.payload }; case 'AGE': return {...state, age : action.payload } ; default : return state ; } } const todoReducer = ( state = [] , action ) => { switch ( action.type ){ case 'ADD': return [...state,action.payload] ; default : return state ; } } let reducer = combineReducer({ personReducer , todoReducer }) let store = createStore(reducer) ; store.dispatch({ type : 'NAME' , payload : 'Jeno' }); store.dispatch({ type : 'AGE' , payload : 22 }); store.dispatch({ type : 'ADD' , payload : 'Coding' }); console.log(store.getState());
|
接下來實作combineReducer
的部分,首先我們先回傳一個函式來代表合併的reducer
1 2 3 4 5
| const combineReducer = () => { return function(){ } }
|
接下來一樣如同一般的reducer
一樣接受state
及action
,在這邊創建一個新的newState
回傳。
1 2 3 4 5 6
| const combineReducer = (reducers) => { return function(state = {} , action){ let newState = {}; return newState ; } }
|
接下來就是遍歷整個reducers
,並將傳入中action
和相對應的state
傳入該reducer
得到的狀態放在newState
並回傳newState
即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| reducers = { personReducer : function( state = {} , action ){ switch ( action.type ){ case 'NAME': return {...state, name : action.payload }; case 'AGE': return {...state, age : action.payload } ; default : return state ; } }, todoReducer : function( state = [] , action ){ switch ( action.type ){ case 'ADD': return [...state,action.payload] ; default : return state ; } } } */ const combineReducer = (reducers) => { return function(state = {} , action){ let newState = {}; for ( let key in reducers ) { newState[key] = reducers[key](state[key],action) ; } return newState ; } }
|
bindActionCreators
bindActionCreators
可以直接把創造action
的函式直接和dispatch
綁在一起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const setPersonName = (name) => { return { type : 'NAME' , payload : name } } const setPersonAge = (age) => { return { type : 'AGE' , payload : age } } const addTodo = (text) => { return { type : 'ADD' , payload : text } } const boundActionCreators = bindActionCreators({ setPersonName , setPersonAge , addTodo },store.dispatch) ; boundActionCreators.addTodo('Coding') ; boundActionCreators.setPersonAge(22); boundActionCreators.setPersonName('Jeno'); console.log(store.getState());
|
接下來是實作的部分,我們首先創建一個空物件boundActionCreators
並回傳。
1 2 3 4
| const bindActionCreators = (actionCreators,dispatch) => { let boundActionCreators = {} ; return boundActionCreators ; }
|
再來遍歷actionCreators
並把相對應的key
宣告為函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| actionCreators = { setPersonName : function(name){ return { type : 'NAME' , payload : name } }, setPersonAge : function(age){ return { type : 'AGE' , payload : age } }, addTodo : function(text){ return { type : 'ADD' , payload : text } } } */ const bindActionCreators = (actionCreators,dispatch) => { let boundActionCreators = {} ; for ( let key in actionCreators ){ boundActionCreators[key] = () => { } } return boundActionCreators ; }
|
該函式的內容則是直接利用dispatch
去發出該函式回傳的action
。
1 2 3 4 5 6 7 8 9
| const bindActionCreators = (actionCreators,dispatch) => { let boundActionCreators = {} ; for ( let key in actionCreators ){ boundActionCreators[key] = (payload) => { dispatch(actionCreators[key](payload)) ; } } return boundActionCreators ; }
|
applyMiddleware
applyMiddleware
可以將dispacth
包裝起來,如同洋蔥一樣必須一層一層透過middlewares
才會到最後的store.dispatch
。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const middleware1 = (middlewareAPI) => (next) => (action) => { console.log('I am middleware 1, my next is ',next) ; return next(action) ; } const middleware2 = (middlewareAPI) => (next) => (action) => { console.log('I am middleware 2, my next is ',next) ; return next(action); } let store = createStore(reducer,{},applyMiddleware(middleware1,middleware2)) ; store.dispatch({ type : 'NAME' , payload : 'Jeno '}) ; console.log(store.getState());
|
首先假設我們現在只有一個middleware
,middleware
傳入兩個參數dispatch
和action
如下,代表希望dispatch
可以透過這個middleware
做處理。
1 2 3 4 5 6 7
| const middleware = (dispatch,action) => { console.log('I am middleware, before dispatch'); dispatch(action); console.log('I am middleware, after dispatch'); } let store = createStore(reducer,{},middleware) ;
|
我們先創建另一個函式專門針對含有middleware
的做處理,若要使用middleware
則必須使用該函式。
1 2 3 4 5 6 7 8 9 10 11
| const createStoreWithMiddleWare = (reducer,state,middleware) => { } const middleware = (dispatch,action) => { console.log('I am middleware, before dispatch'); dispatch(action); console.log('I am middleware, after dispatch'); } let store = createStoreWithMiddleWare(reducer,{},middleware) ;
|
接下來實作createStoreWithMiddleWare
的部分,先使用原本createStore
創造store
,將dispatch
修改為將原先的dispatch
和action
傳入middleware
做處理,再將修改後的store
回傳。
1 2 3 4 5 6 7 8 9 10
| const createStoreWithMiddleWare = (reducer,state,middleware) => { let store = createStore(reducer,state) ; let dispatch = function(action){ middleware(store.dispatch,action) ; } return { ...store, dispatch } }
|
我們可以在createStore
做處理,如此一來就不需要呼叫兩個不同的函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| const createStore = (reducer,state,middleware) => { if ( middleware !== undefined ){ return createStoreWithMiddleWare(reducer,state,middleware) ; } let nowReducer = reducer ; let nowState = state ; let nowListeners = [] ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; nowListeners.forEach((listener) => listener()); } const subscribe = (listener) => { nowListeners.push(listener) ; return function(){ nowListeners.splice(nowListeners.indexOf(listener),1); } } const replaceReducer = (reducer) => { nowReducer = reducer ; dispatch({ type : 'INIT' }) ; } dispatch({ type : 'INIT' }) ; return { getState , dispatch , subscribe , replaceReducer } } let store = createStore(reducer,{},middleware) ;
|
若我們想要傳入多個middlewares
,我們可以修改每一個middleware
成新的函式,將下一個middleware
帶進去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const createStoreWithMiddleWare = (reducer,state,middlewares) => { let store = createStore(reducer,state) ; let newMiddleware = [...middlewares,store.dispatch] ; for ( let i = 0 ; i < newMiddleware.length - 1 ; i ++ ){ let fn = newMiddleware[i] ; newMiddleware[i] = function(action,dispatch){ fn(action,newMiddleware[i+1]) ; } } let dispatch = function(action){ newMiddleware[0](action,newMiddleware[1]) ; } return { ...store, dispatch } } const middleware1 = (action,dispatch) => { console.log('I am middleware 1, before dispatch'); dispatch(action); console.log('I am middleware 1, after dispatch'); } const middleware2 = (action,dispatch) => { console.log('I am middleware 2, before dispatch'); dispatch(action); console.log('I am middleware 2, after dispatch'); }
|
我們在這邊把action
和dispatch
參數對調,是為了因應dispatch(action)
第一個參數是action
的原因。
或者,我們也可以在這邊先將middleware
函式Curry
化,如此一來我們就可以先傳入store.patch
,之後再傳入action
,以便於直接搭配compose
來使用。
關於Curry
可以看我另一篇文章:[JavaScript] Curry
關於Compose
可以看我的另一篇文章:[JavaScript] Compose 和 Pipe
1 2 3 4 5 6 7 8 9 10 11
| const createStoreWithMiddleWare = (reducer,state,middleware) => { let store = createStore(reducer,state) ; let dispatch = store.dispatch ; dispatch = function(action){ middleware(store.dispatch)(action) ; } return { ...store, dispatch } }
|
接著修改middleware
的函式結構。
1 2 3 4 5
| const middleware = (dispatch) => (action) => { console.log('I am middleware 1, before dispatch'); dispatch(action); console.log('I am middleware 1, after dispatch'); }
|
如果要傳入多個middlewares
。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const middleware1 = (dispatch) => (action) => { console.log('I am middleware 1, before dispatch'); dispatch(action); console.log('I am middleware 1, after dispatch'); } const middleware2 = (dispatch) => (action) => { console.log('I am middleware 2, before dispatch'); dispatch(action); console.log('I am middleware 2, after dispatch'); } let store = createStore(reducer,{},[middleware1,middleware2]) ;
|
修改createStoreWithMiddleWare
,先compose
所有的middlewares
再傳入store.dispatch
。
1 2 3 4 5 6 7 8 9 10 11 12
| const createStoreWithMiddleWare = (reducer,state,middlewares) => { let store = createStore(reducer,state) ; let dispatch = store.dispatch ; let composedMiddleWare = compose(...middlewares)(store.dispatch); dispatch = function(action){ composedMiddleWare(action) ; } return { ...store, dispatch } }
|
帶入store.dispatch
到Compose
完Curry
後的middlewares
,可以經由每次回傳一個函式到上一個middleware
,如此一來就可以組合成一個大的middleware
使傳入的action
可以透過middleware1
->middleware2
->store.dispatch
。
接著,假設我們現在必須對特定的action
做處理,例如
1 2 3 4 5 6 7 8 9
| store.dispatch(() => { setTimeout(()=>{ dispatch({ type : 'AGE' , payload : 22 }) ; console.log(getState()); },1000) }) ;
|
發現在action
裡面的函式並沒有dispatch
和getState
可以使用,因此必須傳這兩個進來。
1 2 3 4 5 6 7 8 9 10 11
| store.dispatch((middlewareAPI) => { let dispatch = middlewareAPI.dispatch ; let getState = middlewareAPI.getState ; setTimeout(()=>{ dispatch({ type : 'AGE' , payload : 22 }) ; console.log(getState()); },1000) }) ;
|
如此一來修改createStoreWithMiddleWare
,在這邊我們傳入帶dispatch
和getState
的middlewareAPI
到每個middleware
,注意在這邊帶的dispatch
必須是修改過後的dispatch
而不是store.dispatch
,才可以確保每次dispatch
都會從第一個middleware
開始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const createStoreWithMiddleWare = (reducer,state,middlewares) => { let store = createStore(reducer,state) ; let dispatch = store.dispatch ; let middlewareAPI = { getState : store.getState , dispatch : (action) => dispatch(action) } let newMiddlewares = middlewares.map((middleware)=>middleware(middlewareAPI)); let composedMiddleWare = compose(...newMiddlewares)(store.dispatch); dispatch = function(action){ composedMiddleWare(action) ; } return { ...store, dispatch } }
|
再來我們必須修改middleware
的架構,再這邊我們把middleware2
修改成可以處理action
是函式的狀況,同時這也是redux-thunk
實作原理。
為了避免dispatch
搞混,我們將前往下一個middleware
的dispatch
改為next
。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const middleware1 = (middlewareAPI) => (next) => (action) => { console.log('I am middleware 1, going to next'); let result = next(action); } const middleware2 = (middlewareAPI) => (next) => (action) => { console.log('I am middleware 2, going to next'); if ( typeof action === 'function' ){ action(middlewareAPI) ; } else { let result = next(action); } }
|
因此每當我們呼叫一般的物件action
,則會通過middleware1
->middleware2
->store.dispatch
。
若action
是函式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let store = createStore(reducer,{},[middleware1,middleware2]) ; console.log(store.getState()); store.dispatch({ type : 'NAME' , payload : 'Jeno '}) ; console.log(store.getState()); store.dispatch((middlewareAPI) => { let dispatch = middlewareAPI.dispatch ; let getState = middlewareAPI.getState ; setTimeout(()=>{ dispatch({ type : 'AGE' , payload : 22 }) ; console.log(getState()); },1000) }) ;
|
流程則是middleware1
->middleware2
->middleware1
->middleware2
->store.dispatch
。
如此一來處理middlewares
的函式其實差不多了,我們把參數傳遞方式改一下並且改createStoreWithMiddleWare
為applyMiddleware
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const applyMiddleware = (reducer,state,middlewares) => { let store = createStore(reducer,state) ; let dispatch = store.dispatch ; let middlewareAPI = { getState : store.getState , dispatch : (action) => dispatch(action) } let newMiddlewares = middlewares.map((middleware)=>middleware(middlewareAPI)); let composedMiddleWare = compose(...newMiddlewares)(store.dispatch); dispatch = function(action){ composedMiddleWare(action) ; } return { ...store, dispatch } } let store = createStore(reducer,{},applyMiddleware(middleware1,middleware2)) ;
|
接著改寫applyMiddleware
的結構,並讓它可以傳reducer
和state
進來。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const applyMiddleware = (...middlewares) => (reducer,state) => { let store = createStore(reducer,state) ; let dispatch = store.dispatch ; let middlewareAPI = { getState : store.getState , dispatch : (action) => dispatch(action) } let newMiddlewares = middlewares.map((middleware)=>middleware(middlewareAPI)); let composedMiddleWare = compose(...newMiddlewares)(store.dispatch); dispatch = function(action){ composedMiddleWare(action) ; } return { ...store, dispatch } }
|
在createStore
的部分也做修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const createStore = (reducer,state,enhancer) => { if ( enhancer !== undefined ){ return enhancer(reducer,state) ; } let nowReducer = reducer ; let nowState = state ; let nowListeners = [] ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; nowListeners.forEach((listener) => listener()); } const subscribe = (listener) => { nowListeners.push(listener) ; return function(){ nowListeners.splice(nowListeners.indexOf(listener),1); } } const replaceReducer = (reducer) => { nowReducer = reducer ; dispatch({ type : 'INIT' }) ; } dispatch({ type : 'INIT' }) ; return { getState , dispatch , subscribe , replaceReducer } }
|
Redux
會再把createStore
傳進applyMiddleware
裡,如此一來就不需要createStore
這段程式了。